﻿-- Author      : Kurapica
-- Create Date : 2009/09/29

--[[
	In-Game Addon System
	Studio Part
--]]
do
	-- Only this file included a version number
	IGAS:NewAddon("IGAS_Studio", 3)

	_VersionType = "Version Beta"

    CreateFrame = IGAS.GUI.CreateFrame
    L = IGAS:NewLocal("IGAS_Studio")
	Log = IGAS:NewLogger("IGAS_Studio")

	Log.TimeFormat = "%X"
	Log:SetPrefix(1, "[IGAS_Studio][Trace]")
	Log:SetPrefix(2, "[IGAS_Studio][Debug]")
	Log:SetPrefix(3, "[IGAS_Studio][Info]")
	Log:SetPrefix(4, "[IGAS_Studio][Warn]")
	Log:SetPrefix(5, "[IGAS_Studio][Error]")
	Log:SetPrefix(6, "[IGAS_Studio][Fatal]")

	Log.LogLevel = 3
	Log:AddHandler(print)

	------------------------------------------------------
	------------------- Naming Space ---------------------
	------------------------------------------------------
	Widget = {}
	Advance = {}
	_Addon:AddSavedVariable("IGAS_STUDIO_DB")
	_FrameInfoList = setmetatable({}, {__mode = "k"})

	------------------------------------------------------
	------------------- Base Frame ---------------------
	------------------------------------------------------
	-- Base Form for all other forms in Studio
    coreF = CreateFrame("Frame", "IGAS_STUDIO", WorldFrame)
	coreF:SetAllPoints(WorldFrame)
	coreF.MouseEnabled = true
	coreF.Visible = false

	------------------------------------------------------
	------------------- MiniMap Icon ---------------------
	------------------------------------------------------
	local icon = CreateFrame("MinimapIcon", "IGAS_MapIcon", coreF)
    icon:SetIcon("Interface\\Icons\\Ability_Creature_Cursed_04")
    icon.Tooltip = "IGAS Studio"
    icon.Visible = true
	function icon:OnClick()
		if coreF.Visible then
			UIParent:Show()
			coreF.Visible = false
		else
			UIParent:Hide()
			coreF.Visible = true
		end
    end

	function icon:OnGameTooltipShow(GameTooltip)
		GameTooltip:AddLine(_VersionType.." ".._Version)
	end

	------------------------------------------------------
	------------------- functions ---------------------
	------------------------------------------------------
	function errorhandler(err)
		return geterrorhandler()(err)
	end

	function GetShadowFrame(frame)
		return (frame and frame.Parent and frame.Parent["IsObjectType"] and frame.Parent:IsObjectType(Widget.ShadowFrame) and frame.Parent) or frame
	end

	function GetTrueFrame(frame)
		return (frame and frame["IsObjectType"] and frame:IsObjectType(Widget.ShadowFrame) and frame.TrueFrame) or frame
	end

	function GetTrueObject(frame)
		return GetTrueFrame(frame):GetObjectType()
	end

	-- Sort Function
	local function compare(t1, t2)
		return (t1 or "") < (t2 or "")
	end

	local function bubble_sort(t)
		local check = true

		if not next(t) then return end

		while check do
			check = false

			for i = 1, getn(t) - 1 do
				if compare(t[i+1], t[i]) then
					t[i], t[i+1] = t[i+1], t[i]
					check = true
				end
			end
		end
	end

	local function Gather_Sort(t, pool, trans)
		local idx = {}
		local n

		for i = 1, getn(pool) do
			idx[i] = 1
		end

		while true do
			n = nil

			for i = 1, getn(pool) do
				if idx[i] then
					if not n or compare(pool[i][idx[i]], pool[n][idx[n]]) then
						n = i
					end
				end
			end

			if n then
				tinsert(t, trans[pool[n][idx[n]]] or pool[n][idx[n]])
				if idx[n] < getn(pool[n]) then
					idx[n] = idx[n] + 1
				else
					idx[n] = nil
				end
			else
				break
			end
		end
	end

	function sortFunc(t1, t2)
		if strsub(t1, 1, 1) == "_" then
			if strsub(t2, 1, 1) == "_" then
				return sortFunc(strsub(t1, 2, -1), strsub(t2, 2, -1))
			else
				return true
			end
		elseif strsub(t2, 1, 1) == "_" then
			return false
		else
			return strupper(t1 or "") < strupper(t2 or "")
		end
	end

	function sort(t)
		if getn(t) <= 100 then
			return bubble_sort(t)
		end

		local pool = {}
		local trans = {}
		local a, p, uname

		-- create group
		for _, name in ipairs(t) do
			if name ~= "" then
				uname = strupper(name)

				trans[uname] = name

				a = strsub(uname, 1, 1)

				pool[a] = pool[a] or {}

				p = pool[a]

				if not next(p) then tinsert(p, {}) end

				if getn(p[getn(p)]) >= 100 then
					tinsert(p, {})
				end
				tinsert(p[getn(p)], uname)
			end
		end

		-- sort
		for _, p in pairs(pool) do
			for _, pl in ipairs(p) do
				bubble_sort(pl)
			end
		end

		-- Gather
		wipe(t)

		if pool["_"] then
			Gather_Sort(t, pool["_"], trans)
		end

		for i = strbyte("A"), strbyte("Z") do
			if pool[strchar(i)] then
				Gather_Sort(t, pool[strchar(i)], trans)
			end
		end

		wipe(trans)
		wipe(pool)
	end

	----- Default Value for widgets' propertiers
	_DefaultValue = {}

	local function FormatValue(proType, value)
		if value == nil then
			if proType == "string" then
				return ""
			elseif proType == "number" then
				return 0
			elseif proType == "boolean" then
				return false
			else
				return nil
			end
		else
			return value
		end
	end

	local _RegProp = {}

	local function RegisterDefaultValueByType(data, objectType, prototype, frame)
		local propertys = objectType.Property

		for propertyName in pairs(prototype.Property) do
			if not _RegProp[propertyName] then
				_RegProp[propertyName] = true
				if propertys[propertyName]["Get"] and propertys[propertyName]["Set"] then
					data[propertyName] = frame[propertyName]
				end
			end
		end

		if prototype.Base then
			return RegisterDefaultValueByType(data, objectType, prototype.Base, frame)
		end
	end

	function RegisterDefaultValue(objectType)
		if not objectType then
			return
		elseif type(objectType) == "string" then
			objectType = IGAS.GUI.Widget[objectType]
		elseif type(objectType) ~= "table" then
			return
		end
		if _DefaultValue[objectType] then
			return
		end

		local frame = CreateFrame(objectType, nil, UIParent)

		_DefaultValue[objectType] = {}

		local style = {"Style_nil"}

		if objectType.Property.Style and objectType.Property.Style.Set and objectType.Property.Style.Get and objectType.Property.Style.Type then
			local styleType = IGAS:GetPropertyType(objectType.Property.Style.Type)
			for _, value in pairs(styleType.EnumType or {}) do
				tinsert(style, value)
			end
		end

		for _, styleValue in ipairs(style) do
			_DefaultValue[objectType][styleValue] = {}
			wipe(_RegProp)

			if styleValue ~= "Style_nil" then
				frame.Style = styleValue
				_DefaultValue[objectType][styleValue]["Style"] = styleValue
			end
			RegisterDefaultValueByType(_DefaultValue[objectType][styleValue], objectType, objectType, frame)
		end

		wipe(_RegProp)

		frame:Dispose()
	end

	function GetDefaultValue(objectType, property, style)
		if not objectType then
			return
		elseif type(objectType) == "string" then
			objectType = IGAS.GUI.Widget[objectType]
		elseif type(objectType) ~= "table" then
			return
		end

		if not _DefaultValue[objectType] then
			RegisterDefaultValue(objectType)
		end

		style = style or "Style_nil"

		return (_DefaultValue[objectType] and _DefaultValue[objectType][style] and _DefaultValue[objectType][style][property]) or nil
	end

	function Format(v)
        if v and type(v) == "string" then
            return string.format("%q", v)
		elseif v and type(v) == "number" then
			return string.format("%.1f", v)
		else
			return tostring(v)
		end
    end

	function SameTable(value, default)
		if value == default then
			-- mean all nil
			return true
		end

		if not value or type(value) ~= "table" or not default or type(default) ~= "table" then
			return false
		end

		for f, v in pairs(default) do
			if type(v) ~= "table" then
				if v ~= value[f] then
					return false
				end
			elseif not SameTable(value[f], v) then
				return false
			end
		end
		
		for f in pairs(value) do
			if default[f] == nil then
				return false
			end
		end

		return true
	end

	function SerializeTable(tt)
		local str = "{\n"

		for name, value in pairs(tt) do
			if type(name) == "string" or type(name) == "number" then
				name = (type(name) == "string" and Format(name)) or tostring(name)
				if type(value) == "table" then
					str = str.."    ["..name.."] = "..string.gsub(SerializeTable(value), "\n", "\n    ")..",\n"
				elseif type(value) == "number" or type(value) == "string" then
					str = str.."    ["..name.."] = "..Format(value)..",\n"
				elseif type(value) == "boolean" then
					value = (value and true) or false
					str = str.."    ["..name.."] = "..Format(value)..",\n"
				end
			end
		end

		str = str.."}"

		return str
	end

	function SerializeProperty(frame, propertyName, name)
		local propType = IGAS:GetPropertyType(GetTrueObject(frame).Property[propertyName]["Type"])

		local value = frame[propertyName]
		local default = GetDefaultValue(GetTrueObject(frame), propertyName, frame.Style)

		if not propType["Type"] then
			-- Check if the property is anything
			if value ~= nil and (type(value) == "string" or type(value) == "number" or type(value) == "boolean") then
				return ""..name.."."..propertyName.." = "..Format(value)
			end
		elseif propType["Type"] == "widget" then
			--[[ Check if the property is a widget, do nothing
			if value then
				-- no need check this
				value = GetShadowFrame(value)

				if _FrameInfoList[value] then
					tinsert(codeList, ""..name.."."..propertyName.." = ".._FrameInfoList[value].Name.."")
				end
			end--]]
		elseif propType["Type"] == "number" then
			-- Check if the property is a number
			if value and value ~= default then
				return ""..name.."."..propertyName.." = "..Format(value)
			end
		elseif propType["Type"] == "string" then
			-- Check if the property is a string
			if value and value ~= default then
				if not propType["NotLocal"] then
					return ""..name.."."..propertyName.." = L["..Format(value).."]"
				else
					return ""..name.."."..propertyName.." = "..Format(value)
				end
			end
		elseif propType["Type"] == "boolean" then
			-- Check if the property is a boolean
			value = (value and true) or false
			default = (default and true) or false

			if value ~= default then
				return ""..name.."."..propertyName.." = "..Format(value)
			end
		elseif propType["Type"] == "enum" then
			-- Check if the property is a enum
			if value and propType["EnumType"][value] and (propertyName == "Style" or value ~= default) then
				return ""..name.."."..propertyName.." = "..Format(value)
			end
		elseif propType["Type"] == "table" then
			-- Check if the property is a table
			if not SameTable(value, default) then
				return ""..name.."."..propertyName.." = "..SerializeTable(value)
			end
		end
	end
end
